package domain;

import domain.player.Disc;

/**
 * A class that is used to represent the gamegrid in a game of ConnectFour.
 * It allows you to add/remove discs to it, check if anybody has won etc. and
 * perform some other operations on it like reverse it or reset it.
 */
public class GameGrid {
	// Constants
	public static final int GRID_ROWS = 6,
			GRID_COLUMNS = 7;
	/** The constant that is returned if a method such as addDisc fails. */
	public static final int FAILURE = -1;
	// Members
	private DomainController logicController;
	private Disc[][] grid = new Disc[GRID_ROWS][GRID_COLUMNS];
	private GameRules gameRules = new Rules4InARow(this);

	/** Constructor. */
	public GameGrid(DomainController logicController) {
		this.logicController = logicController;
	}

	public DomainController getLogicController() {
		return logicController;
	}

	/** Returns the game rules instance */
	public GameRules getGameRules() {
		return gameRules;
	}

	/** Resets the game grid */
	public void resetGameGrid() {
		for (int i = 0; i < GRID_ROWS; i++) {
			for (int j = 0; j < GRID_COLUMNS; j++) {
				grid[i][j] = Disc.NONE;
			}
		}
	}

	/**
	 * Adds a disc to the grid. If succeeded the
	 * current row number is returned, otherwise FAILURE.
	 */
	public int addDisc(int column) {
		for (int i = GRID_ROWS - 1; i >= 0; i--) {
			if (grid[i][column] == Disc.NONE) {
				grid[i][column] = logicController.getCurrentPlayer().getDiscID();
				return i;
			}

		}
		return FAILURE;
	}

	/**
	 * Same as addDisc, but with a choice of disc.
	 */
	public int setDisc(int column, Disc disc) {
		for (int i = GRID_ROWS - 1; i >= 0; i--) {
			if (grid[i][column] == Disc.NONE) {
				grid[i][column] = disc;
				return i;
			}
		}
		return FAILURE;
	}

	/** Returns true if the provided column is full */
	public boolean isFull(int column) {
		return (grid[0][column] != Disc.NONE);
	}

	/** Returns the disc type of the winner */
	public Disc getWinner() {
		return gameRules.getWinner();
	}

	/** Returns true if someone has won the game */
	public boolean hasWon() {
		return gameRules.hasWon();
	}

	/**
	 * Returns true if the move row, column combination wins the game,
	 * otherwise false.
	 */
	public boolean winningMove(int row, int column) {
		return gameRules.hasWon(row, column);
	}

	/**
	 * Returns whether or not the move is valid.
	 */
	public boolean isValidMove(int column) {
		return gameRules.isValidMove(column);
	}

	/**
	 * Returns true if the game is a tie, otherwise false.
	 */
	public boolean isTie() {
		return gameRules.isTie();
	}

	/**
	 * Removes a disc at the specified row and column, if there are no discs above it.
	 * (To be used by the computer when it tests different solutions on its
	 * own gamegrid.)
	 */
	public boolean removeDisc(int row, int col) {
		if (row < GRID_ROWS && col < GRID_COLUMNS && (row == GRID_ROWS - 1 || grid[row][col] != Disc.NONE)) {
			grid[row][col] = Disc.NONE;
			return true;
		} else {
			return false;
		}
	}

	/** Removes the topmost disc in the specified column. */
	public void removeDisc(int column) {
		int i;
		for (i = 0; (i < GRID_ROWS) && (grid[i][column] == Disc.NONE); i++);
		if (i < GRID_ROWS) {
			grid[i][column] = Disc.NONE;
		}
	}

	/**
	 * Copies the grid from another GameGrid.
	 * (Used by the computer when it needs to create a copy of the current
	 * state to test different moves on it.)
	 */
	public void copyFromGrid(GameGrid grid) {
		for (int i = 0; i < GRID_ROWS; i++) {
			for (int j = 0; j < GRID_COLUMNS; j++) {
				this.grid[i][j] = grid.getDisc(i, j);
			}
		}
	}

	/** Returns a Disc object on the row, column combination on the grid */
	public Disc getDisc(int row, int column) {
		return grid[row][column];
	}

	/*
	 * returns true if the gamegrid is empty
	 */
	public boolean isEmpty() {
		for (int i = GRID_COLUMNS - 1; i >= 0; i--) {
			if (grid[GRID_ROWS - 1][i] != Disc.NONE) {
				return false;
			}
		}
		return true;
	}

	/*returns true if the the specified row contians no set Discs*/
	public boolean isRowEmpty(int row) {
		for (int i = GRID_COLUMNS - 1; i >= 0; i--) {
			if (grid[row][i] != Disc.NONE) {
				return false;
			}
		}
		return true;
	}

	/** Setting disc at row and column in the gamegrid. */
	public void setDiscAt(int row, int column, Disc disc) {
		if (disc == Disc.NONE) {
			grid[row][column] = Disc.NONE;
		} else {
			grid[row][column] = (disc == Disc.ONE) ? Disc.ONE : Disc.TWO; // use this to make sure that it is the value and not the reference to an disc that is set
		}
	}

	/** Setting reversed disc at row and column in the gamegrid. */
	public void setDiscReversedAt(int row, int column, Disc disc) {
		if (disc == Disc.NONE) {
			grid[row][column] = Disc.NONE;
		} else {
			grid[row][column] = disc == Disc.TWO ? Disc.ONE : Disc.TWO; // use this to make sure that it is the value and not the referens to an disc that is set
		}
	}

	/** returns the gamegrid reversed(1->2, 2->1)*/
	public GameGrid reversedGameGrid() {
		GameGrid temp = new GameGrid(null);

		for (int i = 0; i < GameGrid.GRID_ROWS; i++) {
			for (int j = 0; j < GameGrid.GRID_COLUMNS; j++) {
				temp.setDiscReversedAt(i, j, grid[i][j]);
			}
		}
		return temp;
	}

	/** returns the grid as an string object */
	@Override
	public String toString() {
		String temp = "Grid: \n";
		for (int i = 0; i < GRID_ROWS; i++) {
			for (int j = 0; j < GRID_COLUMNS; j++) {
				temp += grid[i][j].ordinal() + ", ";
			}
			temp += "\n";
		}
		return temp;
	}
}